package cinvestment;

import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import support.AlgorithmSupport;
import util.Lang;
import util.MathUtil;
import util.NullOutputStream;

/**
 * CapacityInvestment is programmed to solve the stochastic programming problem
 * based on Bassamboo. This method discuss the case without fixed cost. And the
 * class implement the classic L-Shaped algorithm based on single-cut
 * **/
public class CapacityInvestmentNoFixed {

	public CapacityInvestmentNoFixed(double delta, int productTypes,
			int simulationCount, double[] penalties, double c1) {
		this.delta = delta;
		this.N = productTypes;
		this.simulatedCount = simulationCount;
		this.p = Lang.clone(penalties);
		this.c1 = c1;
	}

	private List<Plant> plants;
	private double[][] demands;

	private String structure = AlgorithmSupport.EXTENSIVE;

	/**
	 * Number of kinds of products
	 */
	private int N;

	/**
	 * Penalty cost for unfilled demand
	 * **/
	// private double p = 1.0;
	// try to vary the penalty cost of different products
	private double[] p;

	/**
	 * Number of plants
	 */
	private int n;

	/**
	 * Tolerance for the gap between Upper bound and lower bound
	 * **/
	private double epsilon = 0.0005;

	/**
	 * Basic cost for flexible resource cost
	 * **/
	private double c1;
	private double delta;

	/**
	 * Sampling count of demand
	 * **/
	private int simulatedCount;

	/**
	 * iteration counter
	 * **/
	private int iter = 1;

	private double probability;

	public void setStructure(String str) {
		this.structure = str;
	}

	private double variance_demand = 0.3;

	public void setVarianceDemand(double value) {
		this.variance_demand = value;
	}

	private void init() {
		// this.demands = AlgorithmSupport.getDemands_uniform(simulatedCount, N,
		// 0.0, 2.0);
		this.demands = AlgorithmSupport.getDemands_normal(simulatedCount, N,
				1.0, variance_demand, 0, 2.0);
		// this.createPlants();
		this.plants = AlgorithmSupport.createPlantsByStructure(N, structure);
		this.n = this.plants.size();

		this.probability = BigDecimal
				.valueOf(1.0)
				.divide(BigDecimal.valueOf(simulatedCount), 5,
						BigDecimal.ROUND_HALF_EVEN).doubleValue();
	}

	public List<Double[]> lshapedCalculate() {
		this.init();

		List<Double[]> finalResult = new ArrayList<>();

		double[] Kv = new double[n];
		for (int j = 0; j < n; j++)
			Kv[j] = 1.0;

		double[] flexibleCosts = new double[n];
		for (int j = 0; j < n; j++)
			flexibleCosts[j] = this.getFlexibleCost(plants.get(j)
					.getAvailableProductsNumber());

		double[] Et = new double[n];

		double et = 0.0;
		double Etk = 0.0;

		int sameCount = 0;
		double currentRatio = 0;

		iter = 1;
		try {
			IloCplex master = new IloCplex();
			master.setOut(new NullOutputStream());
			IloLinearNumExpr mobj = master.linearNumExpr();
			IloNumVar[] K = master.numVarArray(n, 0.0, Double.MAX_VALUE);
			IloNumVar theta = master.numVar(-100, Double.MAX_VALUE);
			mobj.addTerm(1.0, theta);
			mobj.addTerms(flexibleCosts, K);
			master.addMinimize(mobj);
			double masterObjV = Double.NEGATIVE_INFINITY;

			// @enhance: plants of the same flexible level has the same capacity
			// X is redundancy variables
			IloNumVar[] X = master.boolVarArray(N);
			int idx = 0;
			for (int i = 1; i <= N; i++) {
				int mk = MathUtil.combination(N, i);
				for (int k = idx; k < idx + mk - 1; k++) {
					master.addEq(K[k], K[k + 1]);
				}
				// if X[i]=1, that level of flexibility would not be invested
				master.addLe(K[idx], master.prod(N, X[i - 1]));
				idx += mk;
			}

			IloLinearNumExpr cov1 = master.linearNumExpr();
			for (int i = 0; i < N; i++) {
				cov1.addTerm(1, X[i]);

			}
			// there are at most 2 levels of flexibility
			master.addLe(cov1, 2);
			// must be adjacent
			for (int i = 0; i < N - 2; i++) {
				master.addLe(master.sum(X[i], X[i + 2]), 1);
			}
			// end enhance

			while (true) {
				et = 0.0;
				Etk = 0.0;
				for (int j = 0; j < n; j++)
					Et[j] = 0.0;

				double theta_value = -100;

				if (iter > 1) {
					master.exportModel("D:\\master_" + iter + ".lp");

					if (master.solve()) {
						Kv = master.getValues(K);
						theta_value = master.getValue(theta);
						masterObjV = master.getObjValue();

						System.out.print("decision variables for iter(" + iter
								+ ") = ");
					} else {
						System.out.println("unsolve status = "
								+ master.getStatus());
					}

					Lang.print(Kv);
				}

				// System.out.println("---------- dual problem --------");
				// dual
				for (int m = 0; m < simulatedCount; m++) {
					IloCplex cplex = new IloCplex();
					cplex.setOut(new NullOutputStream());
					IloNumVar[] pi = cplex.numVarArray(N,
							Double.NEGATIVE_INFINITY, 0.0);
					IloNumVar[] sigma = cplex.numVarArray(n,
							Double.NEGATIVE_INFINITY, 0.0);

					IloLinearNumExpr obj = cplex.linearNumExpr();
					obj.add(cplex.scalProd(demands[m], pi));
					obj.add(cplex.scalProd(Kv, sigma));
					cplex.addMaximize(obj);

					for (int i = 0; i < N; i++) {
						for (int j = 0; j < n; j++) {
							if (this.isArcInConfiguration(i, j))
								cplex.addLe(cplex.sum(pi[i], sigma[j]),
										-this.p[i]);
						}
					}
					if (cplex.solve()) {
						// System.out.println("******* " + m + " ******");
						double[] piv = cplex.getValues(pi);
						double[] siv = cplex.getValues(sigma);
						for (int i = 0; i < N; i++)
							et += demands[m][i] * piv[i];

						for (int j = 0; j < n; j++) {
							Etk += -Kv[j] * siv[j];
						}
						Et = MathUtil.sum(Et, siv);
					}

					cplex.end();
				}
				et = BigDecimal
						.valueOf(et)
						.divide(BigDecimal.valueOf(simulatedCount), 3,
								BigDecimal.ROUND_HALF_EVEN).doubleValue();
				Etk = BigDecimal
						.valueOf(Etk)
						.divide(BigDecimal.valueOf(simulatedCount), 3,
								BigDecimal.ROUND_HALF_EVEN).doubleValue();
				for (int j = 0; j < n; j++)
					Et[j] = BigDecimal
							.valueOf(-Et[j])
							.divide(BigDecimal.valueOf(simulatedCount), 3,
									BigDecimal.ROUND_HALF_EVEN).doubleValue();

				double wt = et - Etk;
				double gap = wt - theta_value;

				double ratio = BigDecimal
						.valueOf(wt)
						.divide(BigDecimal.valueOf(theta_value), 7,
								BigDecimal.ROUND_HALF_EVEN).abs().doubleValue();
				System.out.println("iter=" + iter + ": gap = " + gap
						+ "; ratio=" + ratio);

				if (currentRatio == ratio)
					sameCount++;
				else {
					sameCount = 0;
					currentRatio = ratio;
				}

				if (theta_value >= wt || ratio > 0.9995 || sameCount > 2) {
					Lang.print(Kv);

					Double[] fobj = { masterObjV };
					Double[] fcap = new Double[n];
					fcap = Lang.convertdouble2Double(Kv);
					finalResult.add(fobj);
					finalResult.add(fcap);

					break;
				} else {
					// add optimality cut
					master.addGe(master.sum(theta, master.scalProd(Et, K)), et);
				}

				iter++;

			}
			master.end();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return finalResult;
	}

	/**
	 * Calculate the flexible cost for a plant based on the number of products
	 * it can produce. c1[1+(countj-1)*delta]
	 * 
	 * @param number
	 *            : the number of products the plant can produce
	 * **/
	private double getFlexibleCost(int number) {
		// linear cost in number
		return BigDecimal
				.valueOf(c1)
				.multiply(
						BigDecimal.valueOf(1).add(
								BigDecimal.valueOf(delta).multiply(
										BigDecimal.valueOf(number - 1))))
				.doubleValue();

		// // convex in number
		// return BigDecimal
		// .valueOf(c1)
		// .multiply(
		// BigDecimal.ONE.add(BigDecimal.valueOf(
		// Math.pow(number - 1, 1.5)).multiply(
		// BigDecimal.valueOf(delta)))).doubleValue();
	}

	private boolean isArcInConfiguration(int i, int j) {
		// // without considering some special configuration
		//
		return plants.get(j).isAvailable(i);

		// // pair tailor configuration
		// List<Integer> subSet = plants.get(j).getAvailableProducts();
		// if (subSet.size() == 1 && subSet.get(0) == i)
		// return true;
		// if (i != N - 1) {
		// return subSet.size() == 2 && subSet.contains(i)
		// && subSet.contains(i + 1);
		// } else {
		// return subSet.size() == 2 && subSet.contains(i)
		// && subSet.contains(0);
		// }
	}

}
